<!DOCTYPE html>
<html lang="zh-CN">





<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/img/apple-touch-icon.png">
  <link rel="icon" type="image/png" href="/img/favicon.png">
  <meta name="viewport"
        content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
  <meta name="description" content="安全行业从业者，主要方向PC逆向附带安卓和Linux逆向，时不时喜欢写点BUG代码">
  <meta name="author" content="Cray">
  <meta name="keywords" content="">
  <title>编写基础调试器：第五部分（其他） ~ 逆向安全博客</title>

  <link rel="stylesheet" href="https://cdn.staticfile.org/font-awesome/5.12.1/css/all.min.css"  >
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.4.1/css/bootstrap.min.css"  >
<link rel="stylesheet" href="https://cdn.staticfile.org/mdbootstrap/4.13.0/css/mdb.min.css"  >
<link rel="stylesheet" href="https://cdn.staticfile.org/github-markdown-css/3.0.1/github-markdown.min.css"  >

<link rel="stylesheet" href="//at.alicdn.com/t/font_1067060_qzomjdt8bmp.css">



  <link rel="stylesheet" href="/lib/prettify/tomorrow.min.css"  >

<link rel="stylesheet" href="/css/main.css"  >


  <link rel="stylesheet" href="https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.css"  >


<meta name="generator" content="Hexo 5.2.0"></head>


<body>
  <header style="height: 70vh;">
    <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">


    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
          <li class="nav-item">
            <a class="nav-link" href="/">首页</a>
          </li>
        
          
          
          
          
          <li class="nav-item">
            <a class="nav-link" href="/archives/">归档</a>
          </li>
        
          
          
          
          
          <li class="nav-item">
            <a class="nav-link" href="/tags/">标签</a>
          </li>
        
          
          
          
          
          <li class="nav-item">
            <a class="nav-link" href="/links/">友链</a>
          </li>
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" data-toggle="modal" data-target="#modalSearch">&nbsp;&nbsp;<i
                class="iconfont icon-search"></i>&nbsp;&nbsp;</a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>

    <div class="view intro-2" id="background" false
      style="background: url('https://gitee.com/dancve/BlogImg/raw/master/VM_tools/image-20201123095837741.png') no-repeat center center;
      background-size: cover;">
    
        <div class="full-bg-img">
        <div class="mask rgba-black-light flex-center">
          <div class="container text-center white-text fadeInUp">
            <span class="h2" id="subtitle">
              
                编写基础调试器：第五部分（其他）
              
            </span>

            
              <br>
              

              <p>
                
                  
                  &nbsp;<i class="far fa-chart-bar"></i>
                  <span class="post-count">
                    3.2k 字
                  </span>&nbsp;
                

                
                  
                  &nbsp;<i class="far fa-clock"></i>
                  <span class="post-count">
                      14 分钟
                  </span>&nbsp;
                

                
                  <!-- 不蒜子统计文章PV -->
                  
                  &nbsp;<i class="far fa-eye" aria-hidden="true"></i>&nbsp;
                  <span id="busuanzi_container_page_pv">
                    <span id="busuanzi_value_page_pv"></span> 次
                  </span>&nbsp;
                
              </p>
            
          </div>

          
        </div>
      </div>
    </div>
  </header>

  <main>
    
      

<div class="container-fluid">
  <div class="row">
    <div class="d-none d-lg-block col-lg-2"></div>
    <div class="col-lg-8 nopadding-md">
      <div class="py-5 z-depth-3" id="board">
        <div class="post-content mx-auto" id="post">
          <div class="markdown-body">
            <p>欢迎来到有关如何编写原始调试器的最后一部分。这篇文章将涵盖一些其他主题，这些主题在以前的文章中没有出现，目的是添加一些缺少的核心功能。这里涉及的主题将是如何显示反汇编列表，如何单步执行代码（即，跳过条件分支）以及如何转储和修改进程的任意内存。</p>
<h4 id="反汇编"><a href="#反汇编" class="headerlink" title="反汇编"></a>反汇编</h4><p>为了在x86和x64上显示反汇编转储，此调试器将利用<a target="_blank" rel="noopener" href="https://github.com/BeaEngine/beaengine">BeaEngine</a>反汇编库。这是一个非常方便的开源库，支持16/32/64位Intel指令集以及浮点和向量扩展。在示例代码中，它以DLL的形式开发，代码将被运行时加载使用。为了方便起见，这样做是为了避免重新编译静态库。</p>
<p>反汇编程序代码非常容易使用。BeaEngine具有<em>DISASM</em>结构，需要使用体系结构类型和地址进行初始化。然后将其传递给<em>Disasm</em>函数，该函数用有关地址指令的信息填充结构。由于反汇编程序是动态加载的，并且在同一代码中用于x86 / x64，因此需要检索指向<em>Disasm</em>的函数指针。所有这些初始化代码都可以在构造函数中处理。</p>
<pre><code class="C++">Disassembler::Disassembler(HANDLE hProcess) : m_hProcess&#123; hProcess &#125;
&#123;
    memset(&amp;m_disassembler, 0, sizeof(DISASM));
#ifdef _M_IX86
    m_disassembler.Archi = 0;
    if (m_hDll == nullptr)
    &#123;
        m_hDll = LoadLibrary(L&quot;BeaEngine_x86.dll&quot;);
        m_pDisasm = (pDisasm)GetProcAddress(m_hDll, &quot;_Disasm@4&quot;);
    &#125;
#elif defined _M_AMD64
    m_disassembler.Archi = 64;
    if(m_hDll == nullptr)
    &#123;
        m_hDll = LoadLibrary(L&quot;BeaEngine_x64.dll&quot;);
        m_pDisasm = (pDisasm)GetProcAddress(m_hDll, &quot;Disasm&quot;);
    &#125;
#else
#error &quot;Unsupported architecture&quot;
#endif
&#125;</code></pre>
<p><em>m_hDll</em>和<em>m_pDisasm</em>将是静态的，因为没有必要每个实例检索它。由于该代码旨在在x86 / x64上运行，因此提供了两个单独版本的DLL，一个用于x86，另一个用于x64。</p>
<p>现在，反汇编引擎已加载并初始化，是时候开始真正地反汇编代码了。但是，出现了一个有趣的问题。调试器附加到另一个进程，但是提供给反汇编器的地址是调试器进程的，而不是被调试进程。比如：用户想要反编译被调试进程的<em>0x00411000</em>处的代码时，调试器进程中并没有相关的内存数据，所以不会是直接传参那么简单。</p>
<p>这里的解决方案是，读取目标进程中位于<em>0x00411000</em>的内存，并且必须是能够被反编译的数据。（在实现断点时已经做了类似的事情。地址的原始字节已保存，然后用<em>0xCC</em>操作码替换）。因此，其实实现起来也是通过调用API<a target="_blank" rel="noopener" href="http://msdn.microsoft.com/en-us/library/windows/desktop/ms680553(v=vs.85).aspx">ReadProcessMemory</a>将数据读取到调试器中。</p>
<pre><code class="C++">const bool Disassembler::TransferBytes(const DWORD_PTR dwAddress)
&#123;
    SIZE_T ulBytesRead = 0;
    bool bSuccess = BOOLIFY(ReadProcessMemory(m_hProcess, (LPCVOID)dwAddress, m_bytes.data(), m_bytes.size(), &amp;ulBytesRead));
    if (bSuccess &amp;&amp; ulBytesRead == m_bytes.size())
    &#123;
        return true;
    &#125;
    else
    &#123;
        fprintf(stderr, &quot;Could not read from %p. Error = %X\n&quot;, dwAddress, GetLastError());
    &#125;

    return false;
&#125;</code></pre>
<p>拿到数据之后，反汇编过程不会比BeaEngine<a target="_blank" rel="noopener" href="https://github.com/BeaEngine/beaengine/blob/master/doc/examples.md">demo</a>复杂。获取目标反汇编地址，并通过DLL中的导出函数调用<em>Disasm</em>函数。为函数填充<em>DISASM</em>结构（<em>代码</em>中的<em>m_disassembler</em>），返回当前指令的长度，然后加上上一条指令基址以获得下一条指令的地址，然后重复该过程。</p>
<pre><code class="C++">const bool Disassembler::BytesAtAddress(DWORD_PTR dwAddress, size_t ulInstructionsToDisassemble /*= 15*/)
&#123;
    if (IsInitialized())
    &#123;
        SetDisassembler(dwAddress);
        bool bFailed = false;
        while (!bFailed &amp;&amp; ulInstructionsToDisassemble-- &gt; 0)
        &#123;
            int iDisasmLength = m_pDisasm(&amp;amp;m_disassembler);
            if (iDisasmLength != UNKNOWN_OPCODE)
            &#123;
                fprintf(stderr, &quot;0x%p - %s\n&quot;, dwAddress, m_disassembler.CompleteInstr);
                m_disassembler.EIP += iDisasmLength;
                dwAddress += iDisasmLength;
            &#125;
            else
            &#123;
                fprintf(stderr, &quot;Error: Reached unknown opcode in disassembly.\n&quot;);
                bFailed = true;
            &#125;
        &#125;
    &#125;
    else
    &#123;
        fprintf(stderr, &quot;Could not show disassembly at address. Disassembler Dll was not loaded properly.\n&quot;);
        return false;
    &#125;

    return true;
&#125;</code></pre>
<p>该<em>SetDisassembler</em>功能是在拷贝的内存中找到正确的起始地址。调试器一次性申请4096字节的内存（默认的Windows页面大小），如果要反汇编的数据存在于该范围内，则使用该内存。否则，将再次执行读取并重新初始化内存</p>
<pre><code class="C++">void Disassembler::SetDisassembler(const DWORD_PTR dwAddress)
&#123;
    bool bIsCached = ((dwAddress - m_dwStartAddress) &lt; m_bytes.size());
    bIsCached &amp;= (dwAddress &lt; m_dwStartAddress);
    if (!bIsCached)
    &#123;
        (void)TransferBytes(dwAddress);
        m_disassembler.EIP = (UIntPtr)m_bytes.data();
        m_dwStartAddress = dwAddress;
    &#125;
    else
    &#123;
        m_disassembler.EIP = (UIntPtr)&amp;amp;m_bytes.data()[dwAddress - m_dwStartAddress];
    &#125;
&#125;</code></pre>
<p>这就是全部。调试器现在可以在任何可读地址打印反汇编结果。</p>
<h4 id="步过"><a href="#步过" class="headerlink" title="步过"></a>步过</h4><p><strong>单步</strong>执行是通过硬件寄存器实现。<strong>步过</strong>纯粹在代码中实现，并且是一个方便的功能，允许用户跳过步进到系统函函数。例如，获取以下反汇编：</p>
<pre><code class="asm">0040108D 81 C4 C0 00 00 00    add esp,    0C0h
00401093 3B EC                cmp         ebp,esp  
00401095 E8 76 03 00 00       call        SomeFunction (0401410h)  
0040109A 8B E5                mov         esp,ebp  
...</code></pre>
<p>假设你断在了<em>0x0040108D</em>处。你对<em>SomeFunction</em>没有任何兴趣，并且你不想单步执行它。你希望访问地址<em>0x0040109A</em>及以下的更有趣的部分。因此，您要做的就是在<em>0x00401093处</em>将断点设置为<em>0x0040109A</em>并继续执行。这有效地跳过了<em>0x00401095</em>处的<em>CALL</em>指令，并在紧随其后的指令处命中了断点，因此您可以继续调试。单步执行将这些步骤有效地包装到调试器提供的一项便捷功能中。</p>
<p>为了执行步过，调试器必须知道下一条指令是什么。因为这是用户希望在下一步中断的指令。下一条指令可以是几种类型中的一种</p>
<ol>
<li>无效</li>
<li>非分支指令（即<em>add</em> / <em>mov</em> / <em>lea</em> / <em>push</em> /…）</li>
<li>条件分支指令（即<em>jz</em> / <em>jge</em> / <em>jb</em> /…）</li>
<li>无条件分支指令（即<em>call</em> / <em>jmp</em> / <em>ret</em>）</li>
</ol>
<p>如果这是一条无效指令，则由调试器实现决定下一步做什么。在第二种情况下，下一条指令就是当前指令的地址加上当前指令的长度。第三种情况很有趣，也部分实现了定义。如果用户在条件分支上中断并希望跨步，应如何对待？例如，假设用户正在查看以下反汇编列表，并且在<em>0x00401219</em>上断下：</p>
<pre><code class="asm">00401213 8B 45 F8             mov         eax,dword ptr [a]  
00401216 3B 45 EC             cmp         eax,dword ptr [b]  
00401219 7E 05                jle         test+60h (0401220h)  
0040121B E8 50 FF FF FF       call        d (0401170h)  
00401220 8B F4                mov         esi,esp  </code></pre>
<p>假设*[a]<em>大于</em>[b]，<em>那么将不执行跳转，而下一条指令将为</em>0x0040121B<em>。用户决定他们要步过，因此他们将落在</em>0x0040121B上<em>，这是正确的。现在假设相反：</em>[a]<em>小于或等于</em>[b]<em>。这意味着将采用分支，下一个地址将为</em>0x00401220<em>。如果用户处在</em>0x00401219<em>并决定步过，那么会发生什么？</em>0x0040121B<em>将不会执行，那步过的地址是不一定的。是应该执行该操作，还是应该调试器为用户“修复”并在</em>0x00401220<em>处下断？不同的调试器在这里的做法是不同的。为了安全起见，我个人会选择后一种情况。由于调试器可以访问</em>EFLAGS*寄存器，可以在执行指令之前判断是否执行该分支。示例代码中未实现此特定方案。</p>
<p>最后一种情况是无条件分支的情况。影响实现过渡的两个无条件分支是<em>JMP</em>（无条件跳转）和<em>RET</em>（返回）。在这两种情况下，都可以保证执行点改变：跳转目标或栈上的返回地址。跨过<em>RET</em>指令几乎没有用，因为它不会被命中。同样，在95％的情况下，单步执行<em>JMP</em>指令也将毫无用处。该JMP的返回点很可能不是它后面的指令。对于这些情况，示例代码将单步转换为单步执行。综上所述，<strong>步过</strong>功能实现如下：</p>
<pre><code class="C++">DWORD_PTR Disassembler::GetNextInstruction(const DWORD_PTR dwAddress, bool &amp;bIsUnconditionalBranch)
&#123;
    DWORD_PTR dwNextAddress = 0;
    if (IsInitialized())
    &#123;
        SetDisassembler(dwAddress);
        int iDisasmLength = m_pDisasm(&amp;m_disassembler);
        if (iDisasmLength != UNKNOWN_OPCODE)
        &#123;
            if (m_disassembler.Instruction.BranchType == RetType || m_disassembler.Instruction.BranchType == JmpType)
            &#123;
                bIsUnconditionalBranch = true;
            &#125;
            else
            &#123;
                dwNextAddress = (dwAddress + iDisasmLength);
            &#125;
        &#125;
        else
        &#123;
            fprintf(stderr, &quot;Could not get next instruction. Unknown opcode at %p.\n&quot;);
        &#125;
    &#125;
    else
    &#123;
        fprintf(stderr, &quot;Could not get next instruction. Disassembler Dll was not loaded propertly.\n&quot;);
    &#125;

    return dwNextAddress;
&#125;</code></pre>
<p>完整的步过功能实现如下：</p>
<pre><code class="C++">const bool Debugger::StepOver()
&#123;
    CONTEXT ctx = GetExecutingContext();
    bool bIsUnconditionalBranch = false;
#ifdef _M_IX86
    DWORD_PTR dwStepOverAddress = m_pDisassembler-&gt;GetNextInstruction(ctx.Eip, bIsUnconditionalBranch);
#elif defined _M_AMD64
    DWORD_PTR dwStepOverAddress = m_pDisassembler-&gt;GetNextInstruction(ctx.Rip, bIsUnconditionalBranch);
#else
#error &quot;Unsupported platform&quot;
#endif
    if (bIsUnconditionalBranch)
    &#123;
        return StepInto();
    &#125;
    else if (dwStepOverAddress != 0)
    &#123;
        m_pStepPoint-&gt;Disable();
        m_pStepPoint-&gt;ChangeAddress(dwStepOverAddress);
        (void)m_pStepPoint-&gt;Enable();

        ctx.EFlags &amp;= ~0x100;
        (void)SetExecutingContext(ctx);

        return Continue(true);
    &#125;

    return false;
&#125;</code></pre>
<p><em>m_pStepPoint</em>是步过断点地址</p>
<h4 id="转储和修改内存"><a href="#转储和修改内存" class="headerlink" title="转储和修改内存"></a>转储和修改内存</h4><p>最后一个功能不过是对<a target="_blank" rel="noopener" href="http://msdn.microsoft.com/en-us/library/windows/desktop/ms680553(v=vs.85).aspx">ReadProcessMemory</a>和<a target="_blank" rel="noopener" href="http://msdn.microsoft.com/en-us/library/windows/desktop/ms681674(v=vs.85).aspx">WriteProcessMemory</a>的练习。</p>
<pre><code class="C++">const bool Debugger::PrintBytesAt(const DWORD_PTR dwAddress, size_t ulNumBytes /*= 40*/)
&#123;
    SIZE_T ulBytesRead = 0;
    std::unique_ptr&lt;unsigned char[]&gt; pBuffer = std::unique_ptr&lt;unsigned char[]&gt;(new unsigned char[ulNumBytes]);
    const bool bSuccess = BOOLIFY(ReadProcessMemory(m_hProcess(), (LPCVOID)dwAddress, pBuffer.get(), ulNumBytes, &amp;ulBytesRead));
    if (bSuccess &amp;&amp; ulBytesRead == ulNumBytes)
    &#123;
        for (unsigned int i = 0; i &lt; ulBytesRead; ++i)
        &#123;
            fprintf(stderr, &quot;%02X &quot;, pBuffer.get()[i]);
        &#125;
        fprintf(stderr, &quot;\n&quot;);
        return true;
    &#125;

    fprintf(stderr, &quot;Could not read memory at %p. Error = %X\n&quot;, dwAddress, GetLastError());
    return false;
&#125;

const bool Debugger::ChangeByteAt(const DWORD_PTR dwAddress, const unsigned char cNewByte)
&#123;
    SIZE_T ulBytesWritten = 0;
    const bool bSuccess = BOOLIFY(WriteProcessMemory(m_hProcess(), (LPVOID)dwAddress, &amp;cNewByte, sizeof(unsigned char), &amp;ulBytesWritten));
    if (bSuccess &amp;&amp; ulBytesWritten == sizeof(unsigned char))
    &#123;
        return true;
    &#125;

    fprintf(stderr, &quot;Could not change byte at %p. Error = %X\n&quot;, dwAddress, GetLastError());
    return false;
&#125;</code></pre>
<p><strong>测试功能</strong></p>
<p>将使用与以前的帖子相同的示例程序，并进行一些小的修改：</p>
<pre><code class="C++">#include 

void d()
&#123;
    printf(&quot;d called.\n&quot;);
&#125;

void c()
&#123;
    int i = 0x1234;
    printf(&quot;c called.\n&quot;);
    printf(&quot;i is at %p with value %X.\n&quot;, &amp;i, i);
    d();
    printf(&quot;i is at %p with value %X.\n&quot;, &amp;i, i);
&#125;

void b()
&#123;
    printf(&quot;b called.\n&quot;);
    c();
&#125;

void a()
&#123;
    printf(&quot;a called.\n&quot;);
    b();
&#125;

int main(int argc, char *argv[])
&#123;
    printf(&quot;Addresses: \n&quot;
        &quot;a: %p\n&quot;
        &quot;b: %p\n&quot;
        &quot;c: %p\n&quot;
        &quot;d: %p\n&quot;,
        a, b, c, d);

    getchar();
    while (true)
    &#123;
        a();
        getchar();
    &#125;

    return 0;
&#125;</code></pre>
<p>要测试内存修改，可以在<em>d</em>函数的程序处于损坏状态时修改<em>i</em>变量。输入的命令显示为红色。</p>
<pre><code class="json">a
[A]ddress or [s]ymbol name? s
Name: d
Received breakpoint at address 00401170.
Press c to continue, s to step into, o to step over.
i
Enter address to print bytes at: 0x18fcac
34 12 00 00 CC CC CC CC 0C AD C2 AA 8C FD 18 00 8A 10 40 00 60 FE 18 00 94 FD 18
 00 00 E0 FD 7F CC CC CC CC CC CC CC CC
e
Enter address to change byte at: 0x18fcac
Enter new byte: 0x12
e
Enter address to change byte at: 0x18fcad
Enter new byte: 0x34
c
Received step at address 00401171</code></pre>
<p>被调试程序的输出：</p>
<pre><code class="json">Addresses:
a: 00401000
b: 00401050
c: 004010A0
d: 00401170

a called.
b called.
c called.
i is at 0018FCAC with value 1234.
d called.
i is at 0018FCAC with value 3412.</code></pre>
<p>与Visual Studio调试器对齐后，反汇编和跨步测试非常简单。例如，以下是与<em>a</em>函数相关的反汇编：</p>
<pre><code class="asm">//printf(&quot;a called.\n&quot;);
00401009 68 48 21 40 00       push        402148h  
0040100E FF 15 94 20 40 00    call        dword ptr ds:[402094h]  
00401014 83 C4 04             add         esp,4  
//b();
00401017 E8 14 00 00 00       call        b (0401030h)  
0040101C 5F                   pop         edi  
&#125;
...</code></pre>
<p>在<em>0x00401009</em>上设置断点并步过显示在调试器中的以下行为：</p>
<pre><code class="json">a
[A]ddress or [s]ymbol name? a
Breakpoint address: 0x401009
Received breakpoint at address 00401009.
Press c to continue, s to step into, o to step over.
o
Could not write back original opcode to address 00000000. Error = 1E7
Received breakpoint at address 0040100E.
Press c to continue, s to step into, o to step over.
o
Received breakpoint at address 00401014.
Press c to continue, s to step into, o to step over.
o
Received breakpoint at address 00401017.
Press c to continue, s to step into, o to step over.
o
Received breakpoint at address 0040101C.
Press c to continue, s to step into, o to step over.</code></pre>
<p>最后，可以显示所有这些的反汇编列表：</p>
<pre><code class="json">d
Enter address to print disassembly at: 0x401009
0x00401009 - push 00402148h
0x0040100E - call dword ptr [00402094h]
0x00401014 - add esp, 04h
0x00401017 - call 0067D3A3h
0x0040101C - pop edi
0x0040101D - pop esi
0x0040101E - pop ebx
0x0040101F - mov esp, ebp
0x00401021 - pop ebp
0x00401022 - ret
0x00401023 - int3
0x00401024 - int3
0x00401025 - int3
0x00401026 - int3
0x00401027 - int3</code></pre>
<p>与Visual Studio提供的功能一致。</p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>编写调试器看起来是一项艰巨的任务，但这是可以实现的。除了反汇编引擎（这可以写很长的一篇帖子）之外，所有内容都是从头开始，共用大约2,000行代码（在解决方案上进行”\n”正则表达式搜索会产生2195行）。这些代码包含的功能有</p>
<ul>
<li>添加/删除断点</li>
<li>步入/步过指令</li>
<li>在断点后执行或单步</li>
<li>打印/修改寄存器</li>
<li>打印调用堆栈</li>
<li>将符号与模块的地址/dump符号匹配</li>
<li>打印/修改内存</li>
<li>反汇编某个地址</li>
</ul>
<p>虽然肯定不如WinDbg或Visual Studio调试器，代码量虽然很少，但是内容是令人印象深刻。希望这些系列文章的读者已经对他们经常使用的工具是如何工作以及如何开发出来的有了一些深入的了解。</p>
<p>谢谢阅读！</p>
<p>原文链接：<a target="_blank" rel="noopener" href="http://www.codereversing.com/blog/archives/178">http://www.codereversing.com/blog/archives/178</a></p>

            <hr>
          </div>
          <br>
          <div>
            <p>
            
            
              <span>
                <i class="iconfont icon-tag"></i>
                
                  <a class="hover-with-bg" href="/tags/%E9%80%86%E5%90%91/">逆向</a>
                
              </span>
            
            </p>
            
              <p class="note note-warning">本博客所有文章除特别声明外，均采用 <a target="_blank" href="https://zh.wikipedia.org/wiki/Wikipedia:CC_BY-SA_3.0%E5%8D%8F%E8%AE%AE%E6%96%87%E6%9C%AC" rel="nofollow noopener noopener">CC BY-SA 3.0协议</a> 。转载请注明出处！</p>
            
          </div>
        </div>
      </div>
    </div>
    
      <div class="d-none d-lg-block col-lg-2 toc-container">
        <div id="toc">
  <p class="h5"><i class="far fa-list-alt"></i>&nbsp;目录</p>
  <div id="tocbot"></div>
</div>
      </div>
    
  </div>
</div>

<!-- custom -->


<!-- Comments -->
<div class="col-lg-7 mx-auto nopadding-md">
  <div class="container comments mx-auto" id="comments">
    
      <br><br>
      
      
  <div class="disqus" style="width:100%">
    <div id="disqus_thread"></div>
    <script>
      var disqus_config = function () {
        this.page.url = 'http://cve.gitee.io/cve/2020/11/24/编写基础调试器：第五部分（其它）/';
        this.page.identifier = '/2020/11/24/编写基础调试器：第五部分（其它）/';
      };
      var oldLoad = window.onload;
      window.onload = function () {
        var d = document, s = d.createElement('script');
        s.type = 'text/javascript';
        s.src = '//' + 'crayon-1' + '.disqus.com/embed.js';
        s.setAttribute('data-timestamp', +new Date());
        (d.head || d.body).appendChild(s);
      };
    </script>
    <noscript>Please enable JavaScript to view the <a target="_blank" href="https://disqus.com/?ref_noscript" rel="nofollow noopener noopener">comments
        powered by Disqus.</a></noscript>
  </div>


    
  </div>
</div>

    
  </main>

  
    <a class="z-depth-1" id="scroll-top-button" href="#" role="button">
      <i class="fa fa-chevron-up scroll-top-arrow" aria-hidden="true"></i>
    </a>
  

  
    <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">搜索</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v"
                 for="local-search-input">关键词</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>
  

  <footer class="mt-5">
  <div class="text-center py-3">
    <a href="https://hexo.io" target="_blank" rel="nofollow noopener"><b>Hexo</b></a>
    <i class="iconfont icon-love"></i>
    <a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener"> <b>Fluid</b></a>
    <br>

    
  
    <!-- 不蒜子统计PV -->
    
    &nbsp;<span id="busuanzi_container_site_pv"></span>总访问量 
          <span id="busuanzi_value_site_pv"></span> 次&nbsp;
  
  
    <!-- 不蒜子统计UV -->
    
    &nbsp;<span id="busuanzi_container_site_uv"></span>总访客数 
            <span id="busuanzi_value_site_uv"></span> 人&nbsp;
  
  <br>



    


    <!-- cnzz Analytics icon -->
    

  </div>
</footer>

<!-- SCRIPTS -->
<script src="https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js" ></script>
<script src="https://cdn.staticfile.org/popper.js/1.16.1/umd/popper.min.js" ></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/4.4.1/js/bootstrap.min.js" ></script>
<script src="https://cdn.staticfile.org/mdbootstrap/4.13.0/js/mdb.min.js" ></script>
<script src="/js/main.js" ></script>


  <script src="/js/lazyload.js" ></script>



  
  <script src="https://cdn.staticfile.org/tocbot/4.10.0/tocbot.min.js" ></script>
  <script>
    $(document).ready(function () {
      var navHeight = $('#navbar').height();
      var toc = $('#toc');
      var main = $('main');
      var tocT = navHeight + (toc.offset().top - main.offset().top);
      var tocLimMin = main.offset().top - navHeight;
      var tocLimMax = $('#comments').offset().top - navHeight;
      $(window).scroll(function () {
        var scroH = document.body.scrollTop + document.documentElement.scrollTop;
        if (tocLimMin <= scroH && scroH <= tocLimMax) {
          toc.css({
            'display': 'block',
            'position': 'fixed',
            'top': tocT,
          });
        } else if (scroH <= tocLimMin) {
          toc.css({
            'position': '',
            'top': '',
          });
        } else if (scroH > tocLimMax) {
          toc.css('display', 'none');
        }
      });
      tocbot.init({
        tocSelector: '#tocbot',
        contentSelector: '.post-content',
        headingSelector: 'h1,h2,h3,h4,h5,h6',
        linkClass: 'tocbot-link',
        activeLinkClass: 'tocbot-active-link',
        listClass: 'tocbot-list',
        isCollapsedClass: 'tocbot-is-collapsed',
        collapsibleClass: 'tocbot-is-collapsible',
        scrollSmooth: true,
      });
      if ($('.toc-list-item').length > 0) {
        $('#toc > p').css('visibility', 'visible');
      }
    });
  </script>







  <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" ></script>


<!-- Plugins -->



  <script src="https://cdn.staticfile.org/prettify/188.0.0/prettify.min.js" ></script>
  <script>
    $(document).ready(function () {
      $('pre').addClass('prettyprint  linenums');
      prettyPrint();
    })
  </script>





  <script src="https://cdn.staticfile.org/anchor-js/4.2.2/anchor.min.js" ></script>
  <script>
    anchors.options = {
      placement: "right",
      visible: "hover",
      
    };
    var el = "h1,h2,h3,h4,h5,h6".split(",");
    var res = [];
    for (item of el) {
      res.push(".markdown-body > " + item)
    }
    anchors.add(res.join(", "))
  </script>



  <script src="/js/local-search.js" ></script>
  <script>
    var path = "/local-search.xml";
    var inputArea = document.querySelector("#local-search-input");
    inputArea.onclick = function () {
      getSearchFile(path);
      this.onclick = null
    }
  </script>



  <script src="https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.js" ></script>
  <script>
    $("#post img:not(.no-zoom img, img[no-zoom])").each(
      function () {
        var element = document.createElement("a");
        $(element).attr("data-fancybox", "images");
        $(element).attr("href", $(this).attr("src"));
        $(this).wrap(element);
      }
    );
  </script>












</body>
</html>
